package com.kx.yooli.kx.wechat.utils.snow;
/**
 * @author john_sqwen-childe.wen
 * @date 2024/3/28
 * @PROJECT_NAME wechat
 * @description create SnowflakeIdWorker by john_sqwen
 * and SnowflakeIdWorker value is
 */
/*
 * Twitter snowflake
 * constructor like down
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 1 Bit identification Due to the fact that the long basic type is signed in Java, with the highest bit being the sign bit, positive numbers being 0,
 * and negative numbers being 1, the id is generally a positive number and the highest bit being 0<br>
 * 41 bit time cutoff (millisecond level), note that the 41 bit time cutoff is not a time cutoff that stores the
 * current time, but rather the difference between the storage time cutoff (current time cutoff - start time cutoff)
 *The value obtained here is the start time cutoff, which is usually the time when our id generator starts using it,
 * as specified by our program (as shown in the startTime property of the IdWorker class below).
 * 41 digit time cutoff, can be used for 69 years, year T= (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10-bit data machine bit can be deployed on 1024 nodes, including 5-bit datacenterId and 5-bit workerId<br>
 *12 bit sequence, counting within milliseconds. The 12 bit counting sequence number supports each node to generate 4096
 * ID numbers per millisecond (same machine, same time cut)
 * Adding up to exactly 64 bits, it is a Long type
 *The advantage of SnowFlake is that it is sorted in chronological order as a whole, and there is no ID collision (distinguished by data center ID and machine ID) in the entire distributed system.
 *  It is also highly efficient. After testing, SnowFlake can generate about 260000 IDs per second
 */
public class SnowflakeIdWorker {

    // ==============================Fields===========================================
    /**The number of digits occupied by the machine ID**/
    private final long workerIdBits = 5L;
    /**The number of digits occupied by the data identifier ID**/
    private final long dataCenterIdBits = 5L;
    /**Work machine ID (0-31)*/
    private final long workerId;
    /**Data center ID**/
    private final long dataCenterId;
    /**Sequence within milliseconds (0~4095)*/
    private long sequence = 0L;
    /**Last generated ID deadline*/
    private long lastTimestamp = -1L;


    static SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);
    /**

     *Constructor

     *@ param workerId WorkID (0-31)

     *@ param dataCenterId Data Center ID (0-31)

     */
    public SnowflakeIdWorker(long workerId, long dataCenterId) {
        /* The maximum supported machine ID is 31 (this shift algorithm can quickly calculate the maximum decimal
        number that a few binary numbers can represent) **/
        long maxWorkerId = ~(-1L << workerIdBits);
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));
        }
        /* The maximum supported data identifier ID is 31 **/
        long maxDataCenterId = ~(-1L << dataCenterIdBits);
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", maxDataCenterId));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }
    // ==============================Methods==========================================
    /**
     *Get the next ID (this method is thread safe)
     *@ return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        //If the current time is less than the timestamp generated by the previous ID,
        // it indicates that the system clock has rolled back and an exception should be thrown at this time
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        //If generated at the same time, perform a sequence within milliseconds
        /* The number of digits a sequence occupies in the ID**/
        long sequenceBits = 12L;
        if (lastTimestamp == timestamp) {
            /* Generate a mask for the sequence, where it is 4095 (0b111111111111=0xfff=4095)*/
            long sequenceMask = ~(-1L << sequenceBits);
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //Block to the next millisecond to obtain a new timestamp
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变，毫秒内序列重置
        else {
            sequence = 0L;
        }
        //Last generated ID deadline
        lastTimestamp = timestamp;

        //Shift and concatenate them through OR operation to form a 64 _bit ID
        /*  begin date (2015-01-01) */
        long twepoch = 1489111610226L;
        /* Machine ID shifts 12 bits to the left*/
        /* Shift the data identifier ID to the left by 17 bits (12+5)*/
        long dataCenterIdShift = sequenceBits + workerIdBits;
        /*  Move the time cutoff 22 bits to the left (5+5+12)*/
        long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
        return ((timestamp - twepoch) << timestampLeftShift)
                | (dataCenterId << dataCenterIdShift)
                | (workerId << sequenceBits)
                | sequence;
    }
    /**
     *Block until the next millisecond until a new timestamp is obtained
     *@ param lastTimestamp The deadline for the last ID generation
     *@ return Current timestamp
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    /**
     *Returns the current time in milliseconds
     *@ return Current time (milliseconds)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }
}
